package ru.spbau.database.group3.memoryManager;

import ru.spbau.database.group3.engine.Relation;
import ru.spbau.database.group3.filesAndAccess.Page;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Map;

/**
 * @author: stas
 * @date: 2/3/13
 * @time: 1:03 PM
 */
public class SpaceManager {
    public static SpaceManager getInstance() {
        return INSTANCE;
    }

    public MappedByteBuffer getPageData(Integer pid) throws IOException {
        if(directory.containsKey(pid)) {
            PageAddress pageAddress = directory.get(pid);
            return readPageDataFromFile(pageAddress);
        }
        throw new IllegalArgumentException("Wrong page ID" + pid);

    }


    public void flushPageData(MappedByteBuffer value) {
        value.force();
    }

    public static Page allocPage(String tableName, int recordSize) throws IOException {
        if(pageCounter.containsKey(tableName)) {
            int currentSize = pageCounter.get(tableName);
            directory.put(++idCounter, new PageAddress(tableName, currentSize));
            ++currentSize;
            pageCounter.put(tableName, currentSize);
            INSTANCE.writePageHeader(idCounter, getPageHeader(recordSize));
            return new Page(idCounter);
        }
        throw new InternalError("Can't allocate new page in " + tableName);
    }

    private void writePageHeader(int id, PageHeader header) throws IOException {
        MappedByteBuffer pageData = readPageDataFromFile(directory.get(id));
        pageData.putInt(header.getRecordSize());
        pageData.putInt(header.getSlotMapSize());
        pageData.putInt(header.getDataIndex());
        pageData.put(new byte[header.getSlotMapSize()]);
        pageData.force();
    }


    public static void createTable(String tableName) throws IOException {
        Path tablePath = Paths.get(root, tableName);
        Path hfPath = Paths.get(root + tableName + "/HeapFile" + Relation.DATA_EXT);
        Files.createDirectory(tablePath);
        Files.createFile(hfPath);
        pageCounter.put(root + tableName + "/HeapFile" + Relation.DATA_EXT, 0);
    }

    public static void dump(ObjectOutputStream oos) throws IOException {
        oos.writeInt(idCounter);
        oos.writeObject(directory);
        oos.writeObject(pageCounter);
    }

    public static void setState(String root, Map<Integer, PageAddress> directory, Map<String, Integer> pageCounter, int idCounter) {
        SpaceManager.root = root;
        SpaceManager.directory = directory;
        SpaceManager.pageCounter = pageCounter;
        SpaceManager.idCounter = idCounter;
    }



    private static PageHeader getPageHeader(int recordSize) {
        int slotMapMaxLength = ((PAGE_SIZE - 3 * Integer.SIZE / 8) / recordSize) / 8;
        int dataIndex = 3 * Integer.SIZE / 8 + slotMapMaxLength;
        int dataLength = PAGE_SIZE - dataIndex;
        int slotMapSize = (dataLength / recordSize) / 8;
        return new PageHeader(recordSize, slotMapSize, dataIndex);
    }

    private MappedByteBuffer readPageDataFromFile(PageAddress pageAddress) throws IOException {
        FileChannel src = FileChannel.open(Paths.get(pageAddress.getFilename()), StandardOpenOption.READ, StandardOpenOption.WRITE);
        MappedByteBuffer pageData = src.map(FileChannel.MapMode.READ_WRITE, pageAddress.getPositionInFile() * PAGE_SIZE, PAGE_SIZE);
        src.close();
        return pageData;
    }



    private SpaceManager() {}


    private final static int PAGE_SIZE = 4096;
    private final static SpaceManager INSTANCE = new SpaceManager();
    private static int idCounter = 0;
    private static String root;
    private static Map<Integer, PageAddress> directory;
    private static Map<String, Integer> pageCounter;


    public static void createIndex(String path) throws IOException {
        Path indexPath = Paths.get(path);
        Files.createFile(indexPath);
        pageCounter.put(path, 0);
    }
}
